package com.infore.qualityControl.select.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.MapJoin;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import javax.persistence.criteria.SetJoin;
import javax.validation.constraints.NotNull;
import org.springframework.data.domain.PageImpl;
import org.springframework.util.StringUtils;

import com.infore.qualityControl.select.chain.ConditionFilterChain;
import com.infore.qualityControl.select.dto.QueryParamDto;
import com.infore.qualityControl.select.enums.OperatorEnum;
import com.infore.qualityControl.select.filter.condition.BetweenFilter;
import com.infore.qualityControl.select.filter.condition.EqFilter;
import com.infore.qualityControl.select.filter.condition.GeFilter;
import com.infore.qualityControl.select.filter.condition.GtFilter;
import com.infore.qualityControl.select.filter.condition.InFilter;
import com.infore.qualityControl.select.filter.condition.IsNotNullFilter;
import com.infore.qualityControl.select.filter.condition.IsNullFilter;
import com.infore.qualityControl.select.filter.condition.LeFilter;
import com.infore.qualityControl.select.filter.condition.LikeFilter;
import com.infore.qualityControl.select.filter.condition.LtFilter;
import com.infore.qualityControl.select.filter.condition.NotEqFilter;
import com.infore.qualityControl.select.filter.condition.NotInFilter;
import com.infore.qualityControl.select.filter.condition.NotLikeFilter;
import com.infore.qualityControl.util.Page;

public class QueryUtil {

    /**
     * 通过包装类Root获取属性路径
     * 
     * @param nature
     * @param root
     * @return
     */
    @SuppressWarnings("rawtypes")
    public static Path getPropertyPath(String propertyName, Root<?> root) {
        // 注意仅限一层关联，如protocolType.name
        if (propertyName.contains(".")) {
            String[] fieldNames = propertyName.split("\\.");
            Path path = root.get(fieldNames[0]);
            Class clazz = path.getJavaType();
            if (clazz.equals(Set.class)) {
                SetJoin setJoin = root.joinSet(fieldNames[0]);
                path = setJoin.get(fieldNames[1]);
            } else if (clazz.equals(List.class)) {
                ListJoin listJoin = root.joinList(fieldNames[0]);
                path = listJoin.get(fieldNames[1]);
            } else if (clazz.equals(Map.class)) {
                MapJoin mapJoin = root.joinMap(fieldNames[0]);
                path = mapJoin.get(fieldNames[1]);
            } else {
                // 是many to one时
                // path = path.get(fieldNames[1]);
                path = null;
                for (String string : fieldNames) {
                    if (path == null) {
                        path = root.get(string);
                    } else {
                        path = path.get(string);
                    }
                }
            }
            return path;
        } else {
            return root.get(propertyName);
        }
    }

    public static ConditionFilterChain getConditionFilterChain() {
        ConditionFilterChain chain = new ConditionFilterChain();
        chain.addFilter(new EqFilter());
        chain.addFilter(new NotEqFilter());
        chain.addFilter(new GtFilter());
        chain.addFilter(new LtFilter());
        chain.addFilter(new GeFilter());
        chain.addFilter(new LeFilter());
        chain.addFilter(new IsNullFilter());
        chain.addFilter(new IsNotNullFilter());
        chain.addFilter(new LikeFilter());
        chain.addFilter(new NotLikeFilter());
        chain.addFilter(new NotInFilter());
        chain.addFilter(new InFilter());
        chain.addFilter(new BetweenFilter());
        return chain;
    }

    public static Predicate getPredicate(int i, List<String> qssembles, List<Predicate> predicates, List<Predicate> newPredicates,
            CriteriaBuilder criteriaBuilder) {
        Predicate predicate = null;
        String rule = qssembles.get(i).replaceAll(" ", "");
        String[] rules = null;
        Predicate predicate_left = null;
        Predicate predicate_right = null;
        if (rule.contains(OperatorEnum.OR.getOperator())) {
            rules = rule.split(OperatorEnum.OR.getOperator());
            if (rules[0].contains("_")) {
                predicate_left = newPredicates.get(Integer.valueOf(rules[0].substring(1)));
            } else {
                predicate_left = predicates.get(Integer.valueOf(rules[0]));
            }
            if (rules[1].contains("_")) {
                predicate_right = newPredicates.get(Integer.valueOf(rules[1].substring(1)));
            } else {
                predicate_right = predicates.get(Integer.valueOf(rules[1]));
            }
            predicate = criteriaBuilder.or(predicate_left, predicate_right);
            newPredicates.add(predicate);
        } else if (rule.contains(OperatorEnum.AND.getOperator())) {
            rules = rule.split(OperatorEnum.AND.getOperator());
            if (rules[0].contains("_")) {
                predicate_left = newPredicates.get(Integer.valueOf(rules[0].substring(1)));
            } else {
                predicate_left = predicates.get(Integer.valueOf(rules[0]));
            }
            if (rules[1].contains("_")) {
                predicate_right = newPredicates.get(Integer.valueOf(rules[1].substring(1)));
            } else {
                predicate_right = predicates.get(Integer.valueOf(rules[1]));
            }
            predicate = criteriaBuilder.and(predicate_left, predicate_right);
            newPredicates.add(predicate);
        } else if (rule.length() == 1) {
            predicate = predicates.get(Integer.valueOf(0));
        } else {
            throw new RuntimeException("组装查询条件不能为空！");
        }
        return predicate;
    }

    /**
     * 组装select
     * 
     * @param select
     * @param criteriaBuilder
     * @param query
     * @param root
     */
    public static void assembleSelect(String select, CriteriaBuilder criteriaBuilder, CriteriaQuery<?> query, Root<?> root) {
        List<Selection<?>> selections = QueryUtil.getSelections(Arrays.asList(select.split(",")), criteriaBuilder, root);
        query.multiselect(selections);
    }

    /**
     * 组装group
     * 
     * @param query
     * @param root
     * @param group
     */
    @SuppressWarnings("rawtypes")
    public static void assembleGroup(CriteriaQuery<?> query, Root<?> root, String group) {
        if (!StringUtils.isEmpty(group)) {
            List<String> groups = Arrays.asList(group.split(","));
            List<Expression<?>> paths = new ArrayList<>();
            for (String gro : groups) {
                if (gro.contains(".")) { // 判断是否存在多属性(protocolType.code)
                    String[] fields = gro.split("\\.");
                    Path path = null;
                    for (String field : fields) {
                        if (path == null) {
                            path = root.get(field);
                        } else {
                            path = path.get(field);
                        }
                    }
                    paths.add(path);
                    query.groupBy(paths);
                } else {
                    paths.add(root.get(gro));
                    query.groupBy(paths);
                }
            }
        }
    }

    /**
     * 组装order
     * 
     * @param criteriaBuilder
     * @param query
     * @param root
     * @param orders
     */
    @SuppressWarnings("rawtypes")
    public static void assembleOrder(CriteriaBuilder criteriaBuilder, CriteriaQuery<?> query, Root<?> root, List<String> orders) {
        if (orders != null && orders.size() > 0) {
            List<Order> orderList = new ArrayList<>();
            for (String order : orders) {
                String[] orderArr = order.split(",");
                if (orderArr[0].contains(".")) { // 判断属性值是否为副表的，比如protocolType.code
                    String[] fieldNames = orderArr[0].split("\\.");
                    Path path = null;
                    for (String string : fieldNames) {
                        if (path == null) {
                            path = root.get(string);
                        } else {
                            path = path.get(string);
                        }
                    }
                    if (orderArr[1].equalsIgnoreCase("asc")) {
                        orderList.add(criteriaBuilder.asc(path));
                    } else if (orderArr[1].equalsIgnoreCase("desc")) {
                        orderList.add(criteriaBuilder.desc(path));
                    }
                } else {
                    if (orderArr[1].equalsIgnoreCase("asc")) {
                        orderList.add(criteriaBuilder.asc(root.get(orderArr[0])));
                    } else if (orderArr[1].equalsIgnoreCase("desc")) {
                        orderList.add(criteriaBuilder.desc(root.get(orderArr[0])));
                    }
                }
            }
            if (orderList != null && orderList.size() > 0) {
                query.orderBy(orderList);
            }

        }
    }

    /**
     * 获取Selection集合
     * 
     * @param selects
     * @param criteriaBuilder
     * @param root
     * @return
     */
    @SuppressWarnings("unchecked")
    public static List<Selection<?>> getSelections(List<String> selects, CriteriaBuilder criteriaBuilder, Root<?> root) {
        List<Selection<?>> selections = new ArrayList<>(); // 创建查询指定字段集合
        for (String select : selects) {
            if (select.contains("count_")) { // 统计个数
                select = select.replace("count_", "");
                selections.add(criteriaBuilder.count(QueryUtil.getPropertyPath(select, root)));
            } else if (select.contains("max_")) { // 最大
                select = select.replace("max_", "");
                selections.add(criteriaBuilder.max(QueryUtil.getPropertyPath(select, root)));
            } else if (select.contains("min_")) { // 最小
                select = select.replace("min_", "");
                selections.add(criteriaBuilder.min(QueryUtil.getPropertyPath(select, root)));
            } else if (select.contains("sum_")) { // 总和
                select = select.replace("sum_", "");
                selections.add(criteriaBuilder.sum(QueryUtil.getPropertyPath(select, root)));
            } else {
                selections.add(QueryUtil.getPropertyPath(select, root));
            }
        }
        return selections;
    }

    /**
     * 分页查询
     */
    public static List<?> getPage(int totalRecords, List<?> result, TypedQuery<?> typedQuery, QueryParamDto queryParamDto, Page<?> page) {
        PageObject pageObject = queryParamDto.getPageObject();
        if (pageObject != null) {
            // 分页
            org.springframework.data.domain.Page<?> pageResult = QueryUtil.getPageResult(typedQuery, pageObject, totalRecords);
            // 获取分页后的数据集
            result = pageResult.getContent();
            page.setPageNo(pageObject.getPageNumber());
            page.setPageSize(pageObject.getPageSize());
            page.setTotalRecords(totalRecords);
            page.setTotalPages(pageResult.getTotalPages());
        }
        return result;
    }

    /**
     * 获取分页结果集
     * 
     * @param typedQuery
     * @param pageObject
     * @param totalRecords
     * @return
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static org.springframework.data.domain.Page<?> getPageResult(TypedQuery<?> typedQuery, PageObject pageObject, int totalRecords) {
        int pageNumber = pageObject.getPageNumber() - 1;
        int pageSize = pageObject.getPageSize();
        typedQuery.setFirstResult(pageNumber * pageSize);
        typedQuery.setMaxResults(pageSize);
        org.springframework.data.domain.Page pageResult = new PageImpl(typedQuery.getResultList(), pageObject.getPageRequest(),
                totalRecords);
        return pageResult;
    }

    /**
     * 创建查询条件
     * 
     * @param queryParamDto
     * @param criteriaBuilder
     * @param root
     * @return
     */
    public static List<Predicate> createQueryCondition(@NotNull QueryParamDto queryParamDto, @NotNull CriteriaBuilder criteriaBuilder,
            @NotNull Root<?> root) {
        List<Predicate> predicates = new ArrayList<>();
        if (queryParamDto != null) {
            // 取出查询条件
            QueryCondition condition = queryParamDto.getQueryCondition();
            if (condition != null) {
                // 获取查询条件列名,操作符,匹配内容
                List<String> propertys = condition.getProperty();
                List<String> operators = condition.getOperator();
                List<String> contents = condition.getContent();
                if (!StringUtils.isEmpty(propertys) && !StringUtils.isEmpty(operators) && !StringUtils.isEmpty(contents)) {
                    if (propertys.size() == operators.size() && operators.size() == contents.size()) {
                        // 获取过滤链，调用里面注册的过滤器
                        ConditionFilterChain filterChain = QueryUtil.getConditionFilterChain();
                        for (int i = 0; i < propertys.size(); i++) {
                            // 获取一条完整的查询条件，包括列名，操作符，匹配内容
                            String property = propertys.get(i);
                            String operator = operators.get(i);
                            String content = contents.get(i);
                            filterChain.doFilter(predicates, criteriaBuilder, root, property, operator, content, filterChain, false);
                        }
                    } else {
                        throw new RuntimeException("查询条件列名，操作符，匹配内容必须一一匹配！");
                    }
                } else {
                    throw new RuntimeException("查询条件列名，操作符，匹配内容不允许为空！");
                }
            }
        }
        return predicates;
    }

    /**
     * 组装查询条件
     * 
     * @param queryParamDto
     * @param criteriaBuilder
     * @param predicates
     */
    public static Predicate assembleCondition(@NotNull QueryParamDto queryParamDto, @NotNull CriteriaBuilder criteriaBuilder,
            @NotNull List<Predicate> predicates) {
        Predicate predicate = null;
        if (queryParamDto != null) {
            // 取出组装查询条件
            List<String> qssembles = queryParamDto.getAssembleCondition();
            List<Predicate> newPredicates = new ArrayList<>();
            if (qssembles != null) {
                for (int i = 0; i < qssembles.size(); i++) {
                    predicate = QueryUtil.getPredicate(i, qssembles, predicates, newPredicates, criteriaBuilder);
                }
            }
        }
        return predicate;
    }

    /**
     * 组装SQL
     * 
     * @param queryParamDto
     * @param criteriaBuilder
     * @param predicates
     */
    public static void assembleSql(@NotNull QueryParamDto queryParamDto, @NotNull CriteriaBuilder criteriaBuilder,
            @NotNull CriteriaQuery<?> query, @NotNull Root<?> root, @NotNull Predicate predicate) {
        if (queryParamDto != null) {
            // 取出组装sql
            AssembleSql qssembleSql = queryParamDto.getAssembleSql();
            if (qssembleSql != null) {
                // 获取查询条件列名,操作符,匹配内容
                String select = qssembleSql.getSelect();
                String group = qssembleSql.getGroup();
                List<String> order = qssembleSql.getOrder();
                if (!StringUtils.isEmpty(select)) {
                    QueryUtil.assembleSelect(select, criteriaBuilder, query, root);
                }
                if (!StringUtils.isEmpty(group)) {
                    QueryUtil.assembleGroup(query, root, group);
                }
                if (order != null && order.size() > 0) {
                    QueryUtil.assembleOrder(criteriaBuilder, query, root, order);
                }
            }
            if (predicate != null) { // 如果predicate不为空,才添加查询条件
                query.where(predicate);
            }
        }
    }

}
